package org.zhu.mybatis_test;

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.zhu.mybatis_test.bean.Pageable;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Optional;
import java.util.Properties;

/**
 * @author yingzhi zhu
 * date 2018/1/19.
 */
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class}) })
public class PageInterceptor implements Interceptor {

    /**
     * 拦截后的实际处理
     * @param invocation 通过反射机制获取的@Signature中拦截的类，方法，参数的实例
     * @return 拦截方法的返回值 这里的返回值则为StatementHandler.prepare方法执行后的返回值.
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //获取数据库连接
        Connection connection = (Connection) invocation.getArgs()[0];
        //获取拦截的StatementHandler接口示例对象
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        //获取mybatis中存储sql语句实例的对象
        BoundSql boundSql = statementHandler.getBoundSql();
        //获取mybatis中存储mapper中参数 实例的对象
        ParameterHandler parameterHandler = statementHandler.getParameterHandler();
        //Pageable空对象检测
        Optional.ofNullable(getPageable(statementHandler)).ifPresent(
                pageable->{
                    //设置分页的总数
                    setPageTotal(connection,boundSql,pageable,parameterHandler);
                    //设置分页sql语句
                    setSqlStatement(boundSql,pageable);
                }
        );
        //执行拦截的方法 并返回
        return invocation.proceed();
    }

    /**
     * 设置分页sql语句
     * @param boundSql
     * @param pageable
     */
    private void setSqlStatement(BoundSql boundSql,Pageable pageable){
        int start = (pageable.getPage()-1)*pageable.getSize();
        int size = pageable.getSize();
        String pagingSql = boundSql.getSql() + " Limit " + start + "," + size;
        //方法1：利用mybatis原生的方法 强行将sql语句写入boundSql中 private final String sql 的字段
        MetaObject metaObject =
                    MetaObject.forObject(boundSql, SystemMetaObject.DEFAULT_OBJECT_FACTORY,
                            SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,new DefaultReflectorFactory());
        metaObject.setValue("sql",pagingSql);
        //方法2：自己写通过java反射机制 强行将sql语句写入boundSql中
//      writeDeclaredField(boundSql, "sql", pagingSql);
    }

    /**
     * 分页的总数设置
     * @param connection 数据库连接
     * @param boundSql 绑定的sql
     * @param pageable 分页对象
     * @param parameterHandler 参数
     */
    private void setPageTotal(Connection connection,BoundSql boundSql,Pageable pageable,ParameterHandler parameterHandler){
        String countSql = convertToCountSql(boundSql.getSql());
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            preparedStatement = connection.prepareStatement(countSql);
            //利用mybatis原生方法 对sql语句设置参数
            parameterHandler.setParameters(preparedStatement);
            resultSet = preparedStatement
                    .executeQuery();
            if (resultSet.next()){
                pageable.setTotal(resultSet.getInt(1));
                pageable.setTotalPages((int)Math.ceil((double)pageable.getTotal()/(double)pageable.getSize()));
            }

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //释放资源
            if(resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(preparedStatement != null) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 将原有语句转化成 统计数量的语句 select XX From XXXXXXXX -> select count(*) From XXXXXXXX
     * @param originSql
     * @return
     */
    private String convertToCountSql(String originSql){
        originSql = originSql.toLowerCase();
        StringBuilder countSql = new StringBuilder("select count(*) ");
        String[] sql = originSql.split("from");
        sql[0] = "";
        boolean flag = true;
        for (String ss:sql){
            if (flag){
                flag = false;
                countSql.append(ss);
            }else {
                countSql.append("from").append(ss);
            }
        }
        return countSql.toString();
    }

    /**
     * 设置哪些Mybatis对象需要被该插件拦截
     * @param target Mybatis对象
     * @return
     */
    @Override
    public Object plugin(Object target) {
        //只拦截StatementHandler中并且参数中有Pageable对象的方法
        if (target instanceof StatementHandler){
            if (getPageable((StatementHandler) target) != null){
                return Plugin.wrap(target, this);
            }
        }
        //放行
        return target;
    }
    /**
     * 获取方法中传递过来的Pageable对象参数 没有返回NULL
     * @param statementHandler
     * @return
     */
    private Pageable getPageable(StatementHandler statementHandler){
        //取得传递过来的参数对象
        Object o = statementHandler.getParameterHandler().getParameterObject();
        //方式一 如果单只有一个参数
        if (o instanceof Pageable){
            return (Pageable) o;
        }
        //方式二 如果有多个参数 则会封装成Map
        if (o instanceof HashMap){
            for (Object value:((HashMap) o).values()){
                if (value instanceof Pageable){
                    return (Pageable) value;
                }
            }
        }
        return null;
    }

    @Override
    public void setProperties(Properties properties) {
    }

    /**
     * 通过反射强行的把值赋予给目标对象的某个属性
     * @param target 目标对象
     * @param fieldName 属性名
     * @param value 值
     * @throws IllegalAccessException
     */
    private void writeDeclaredField(Object target, String fieldName, Object value)
            throws IllegalAccessException {
        if (target == null) {
            throw new IllegalArgumentException("target object must not be null");
        }
        Class<?> cls = target.getClass();
        Field field = getField(cls, fieldName);
        if (field == null) {
            throw new IllegalArgumentException("Cannot locate declared field " + cls.getName() + "." + fieldName);
        }
        field.set(target, value);
    }

    /**
     * 获取某个对象的某个域
     * @param cls 对象
     * @param fieldName 属性名
     * @return
     */
    private static Field getField(final Class<?> cls, String fieldName) {
        for (Class<?> acls = cls; acls != null; acls = acls.getSuperclass()) {
            try {
                Field field = acls.getDeclaredField(fieldName);
                if (!Modifier.isPublic(field.getModifiers())) {
                    field.setAccessible(true);
                    return field;
                }
            } catch (NoSuchFieldException ex) {
                // ignore
            }
        }
        return null;
    }
}
